﻿using System;
using System.Collections.Specialized;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Markup;
using JetBrains.Annotations;

namespace MahApps.Metro.Controls
{
    public interface IDataGridColumnStylesHelper
    {
        /// <summary>
        /// Attach this helper to the DataGrid.
        /// </summary>
        /// <param name="aDataGrid"></param>
        void Attach(DataGrid aDataGrid);

        /// <summary>
        /// Detach the helper from the attached DataGrid
        /// </summary>
        void Detach();
    }

    [MarkupExtensionReturnType(typeof(DataGridColumnStylesHelperExtension))]
    public class DataGridColumnStylesHelperExtension : MarkupExtension, IDataGridColumnStylesHelper
    {
        private DataGrid dataGrid;

        /// <inheritdoc />
        public void Attach(DataGrid aDataGrid)
        {
            this.dataGrid = aDataGrid ?? throw new ArgumentNullException(nameof(aDataGrid));

            this.dataGrid.Columns.CollectionChanged -= this.OnColumnsCollectionChanged;
            this.dataGrid.Columns.CollectionChanged += this.OnColumnsCollectionChanged;

            foreach (var column in this.dataGrid.Columns)
            {
                this.BindColumnStyles(column);
            }
        }

        /// <inheritdoc />
        public void Detach()
        {
            this.dataGrid.Columns.CollectionChanged -= this.OnColumnsCollectionChanged;

            foreach (var column in this.dataGrid.Columns)
            {
                this.ClearColumnStyles(column);
            }
        }

        private void OnColumnsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.OldItems != null)
            {
                foreach (var column in e.OldItems.OfType<DataGridColumn>())
                {
                    this.ClearColumnStyles(column);
                }
            }

            if (e.NewItems != null)
            {
                foreach (var column in e.NewItems.OfType<DataGridColumn>())
                {
                    this.BindColumnStyles(column);
                }
            }
        }

        protected virtual void BindColumnStyles(DataGridColumn column)
        {
            switch (column)
            {
                case DataGridTextColumn textColumn:
                    SetBinding(textColumn, DataGridBoundColumn.ElementStyleProperty, this.dataGrid, DataGridHelper.AutoGeneratedTextColumnStyleProperty);
                    SetBinding(textColumn, DataGridBoundColumn.EditingElementStyleProperty, this.dataGrid, DataGridHelper.AutoGeneratedTextColumnEditingStyleProperty);
                    break;
                case DataGridCheckBoxColumn checkBoxColumn:
                    SetBinding(checkBoxColumn, DataGridBoundColumn.ElementStyleProperty, this.dataGrid, DataGridHelper.AutoGeneratedCheckBoxColumnStyleProperty);
                    SetBinding(checkBoxColumn, DataGridBoundColumn.EditingElementStyleProperty, this.dataGrid, DataGridHelper.AutoGeneratedCheckBoxColumnEditingStyleProperty);
                    break;
                case DataGridComboBoxColumn comboBoxColumn:
                    SetBinding(comboBoxColumn, DataGridComboBoxColumn.ElementStyleProperty, this.dataGrid, DataGridHelper.AutoGeneratedComboBoxColumnStyleProperty);
                    SetBinding(comboBoxColumn, DataGridComboBoxColumn.EditingElementStyleProperty, this.dataGrid, DataGridHelper.AutoGeneratedComboBoxColumnEditingStyleProperty);
                    break;
                case DataGridNumericUpDownColumn numericUpDownColumn:
                    SetBinding(numericUpDownColumn, DataGridBoundColumn.ElementStyleProperty, this.dataGrid, DataGridHelper.AutoGeneratedNumericUpDownColumnStyleProperty);
                    SetBinding(numericUpDownColumn, DataGridBoundColumn.EditingElementStyleProperty, this.dataGrid, DataGridHelper.AutoGeneratedNumericUpDownColumnEditingStyleProperty);
                    break;
                case DataGridHyperlinkColumn hyperlinkColumn:
                    SetBinding(hyperlinkColumn, DataGridBoundColumn.ElementStyleProperty, this.dataGrid, DataGridHelper.AutoGeneratedHyperlinkColumnStyleProperty);
                    SetBinding(hyperlinkColumn, DataGridBoundColumn.EditingElementStyleProperty, this.dataGrid, DataGridHelper.AutoGeneratedHyperlinkColumnEditingStyleProperty);
                    break;
            }
        }

        protected virtual void ClearColumnStyles(DataGridColumn column)
        {
            switch (column)
            {
                case DataGridTextColumn textColumn:
                    ClearBinding(textColumn, DataGridBoundColumn.ElementStyleProperty, this.dataGrid, DataGridHelper.AutoGeneratedTextColumnStyleProperty);
                    ClearBinding(textColumn, DataGridBoundColumn.EditingElementStyleProperty, this.dataGrid, DataGridHelper.AutoGeneratedTextColumnEditingStyleProperty);
                    break;
                case DataGridCheckBoxColumn checkBoxColumn:
                    ClearBinding(checkBoxColumn, DataGridBoundColumn.ElementStyleProperty, this.dataGrid, DataGridHelper.AutoGeneratedCheckBoxColumnStyleProperty);
                    ClearBinding(checkBoxColumn, DataGridBoundColumn.EditingElementStyleProperty, this.dataGrid, DataGridHelper.AutoGeneratedCheckBoxColumnEditingStyleProperty);
                    break;
                case DataGridComboBoxColumn comboBoxColumn:
                    ClearBinding(comboBoxColumn, DataGridComboBoxColumn.ElementStyleProperty, this.dataGrid, DataGridHelper.AutoGeneratedComboBoxColumnStyleProperty);
                    ClearBinding(comboBoxColumn, DataGridComboBoxColumn.EditingElementStyleProperty, this.dataGrid, DataGridHelper.AutoGeneratedComboBoxColumnEditingStyleProperty);
                    break;
                case DataGridNumericUpDownColumn numericUpDownColumn:
                    ClearBinding(numericUpDownColumn, DataGridBoundColumn.ElementStyleProperty, this.dataGrid, DataGridHelper.AutoGeneratedNumericUpDownColumnStyleProperty);
                    ClearBinding(numericUpDownColumn, DataGridBoundColumn.EditingElementStyleProperty, this.dataGrid, DataGridHelper.AutoGeneratedNumericUpDownColumnEditingStyleProperty);
                    break;
                case DataGridHyperlinkColumn hyperlinkColumn:
                    ClearBinding(hyperlinkColumn, DataGridBoundColumn.ElementStyleProperty, this.dataGrid, DataGridHelper.AutoGeneratedHyperlinkColumnStyleProperty);
                    ClearBinding(hyperlinkColumn, DataGridBoundColumn.EditingElementStyleProperty, this.dataGrid, DataGridHelper.AutoGeneratedHyperlinkColumnEditingStyleProperty);
                    break;
            }
        }

        /// <summary>
        /// Sets binding from the source dependency property to the dependency property of the given target.
        /// </summary>
        /// <param name="target">The target object.</param>
        /// <param name="targetDependencyProperty">The target's dependency property.</param>
        /// <param name="source">The source object.</param>
        /// <param name="sourceDependencyProperty">The dependency property of the source.</param>
        protected static void SetBinding([NotNull] DependencyObject target, [NotNull] DependencyProperty targetDependencyProperty, [NotNull] DependencyObject source, [NotNull] DependencyProperty sourceDependencyProperty)
        {
            if (target == null)
            {
                throw new ArgumentNullException(nameof(target));
            }

            if (targetDependencyProperty == null)
            {
                throw new ArgumentNullException(nameof(targetDependencyProperty));
            }

            if (source == null)
            {
                throw new ArgumentNullException(nameof(source));
            }

            if (sourceDependencyProperty == null)
            {
                throw new ArgumentNullException(nameof(sourceDependencyProperty));
            }

            if (target.ReadLocalValue(targetDependencyProperty) == DependencyProperty.UnsetValue)
            {
                BindingOperations.SetBinding(target,
                                             targetDependencyProperty,
                                             new Binding { Path = new PropertyPath(sourceDependencyProperty), Source = source });
            }
        }

        /// <summary>
        /// Clears the binding from the source dependency property to the dependency property of the given target.
        /// </summary>
        /// <param name="target">The target object.</param>
        /// <param name="targetDependencyProperty">The target's dependency property.</param>
        /// <param name="source">The source object.</param>
        /// <param name="sourceDependencyProperty">The dependency property of the source.</param>
        protected static void ClearBinding([NotNull] DependencyObject target, [NotNull] DependencyProperty targetDependencyProperty, [NotNull] DependencyObject source, [NotNull] DependencyProperty sourceDependencyProperty)
        {
            if (target == null)
            {
                throw new ArgumentNullException(nameof(target));
            }

            if (targetDependencyProperty == null)
            {
                throw new ArgumentNullException(nameof(targetDependencyProperty));
            }

            if (source == null)
            {
                throw new ArgumentNullException(nameof(source));
            }

            if (sourceDependencyProperty == null)
            {
                throw new ArgumentNullException(nameof(sourceDependencyProperty));
            }

            var binding = BindingOperations.GetBinding(target, targetDependencyProperty);
            if (binding != null && Equals(binding.Source, source))
            {
                target.ClearValue(targetDependencyProperty);
            }
        }

        /// <inheritdoc />
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            return this;
        }
    }
}